package com.yth.utils;

import freemarker.core.InvalidReferenceException;
import freemarker.template.*;
import freemarker.template.utility.DeepUnwrap;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

/**
 * @author yutianhong
 * @version 1.0
 * @since 2025/6/27 10:15
 */
@Slf4j
public class FreemarkerHelper {
    public static FreemarkerHelper newInstance() {
        return new FreemarkerHelper(false);
    }

    public static FreemarkerHelper newInstance(boolean strictMode) {
        return new FreemarkerHelper(strictMode);
    }

    private final Configuration cfg;
    private final Map<String, TemplateMethodModelEx> functionMap;
    private final Map<String, Class<?>> utilsMap;

    private FreemarkerHelper(boolean strictMode) {
        // 创建配置对象
        Configuration cfg = new Configuration(Configuration.VERSION_2_3_28);
        // 设置字符编码
        cfg.setDefaultEncoding("UTF-8");
        // 设置模板异常处理器
        if (strictMode) {
            cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
        } else {
            cfg.setTemplateExceptionHandler((te, env, out) -> {
                try {
                    if (te instanceof InvalidReferenceException) {
                        InvalidReferenceException ire = (InvalidReferenceException) te;
                        String varName = ire.getBlamedExpressionString();
                        out.write("${" + varName + "}");
                    } else {
                        throw te;
                    }
                } catch (IOException e) {
                    throw new TemplateException("Error writing template exception fallback", e, env);
                }
            });
        }

        this.cfg = cfg;
        this.functionMap = new ConcurrentHashMap<>();
        this.utilsMap = new ConcurrentHashMap<>();
    }

    public void registerFunction(String name, Function<List<Object>, Object> function) {
        TemplateMethodModelEx previous = functionMap.putIfAbsent(name, arguments -> {

            if (arguments == null || arguments.isEmpty()) {
                return function.apply(Collections.emptyList());
            }
            List<Object> param = new ArrayList<>(arguments.size());
            for (Object argument : arguments) {
                param.add(DeepUnwrap.unwrap((TemplateModel) argument));
            }
            return function.apply(param);
        });
        if (previous != null) {
            throw new RuntimeException("method " + name + " already exists");
        }
    }

    public void registerUtils(String name, Class<?> utilClass) {
        Class<?> previous = utilsMap.putIfAbsent(name, utilClass);
        if (previous != null) {
            throw new RuntimeException("utils " + name + " already exists");
        }
    }

    public String renderTemplate(String templateContent, Map<String, Object> param) {
        Map<String, Object> dataModel = new HashMap<>();
        utilsMap.forEach((key, value) -> dataModel.put(key, value.getName()));
        dataModel.putAll(functionMap);
        if (param != null) {
            dataModel.putAll(param);
        }
        Template template;
        try {
            template = new Template("basic", new StringReader(templateContent), cfg);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        StringWriter out = new StringWriter();
        try {
            template.process(dataModel, out);
        } catch (TemplateException | IOException e) {
            log.error("renderTemplate error", e);
            throw new RuntimeException(e);
        }
        return out.toString();
    }
}
